#include <urlmon.h>
#include <tchar.h>
#include "postmon.h"
#include "bsc.h"
#pragma warning(disable: 4996)

// ===========================================================================
//                     CBindStatusCallback Implementation
// ===========================================================================

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::CBindStatusCallback
// ---------------------------------------------------------------------------
CBindStatusCallback::CBindStatusCallback(CStatus* pStatus, BINDVERB dwBindVerb)
{
	m_pStatus = pStatus;
    m_pbinding = NULL;
    m_pstm = NULL;
    m_cRef = 1;
    m_cbOld = 0;

	m_dwAction = (BINDVERB_GET == dwBindVerb || BINDVERB_POST == dwBindVerb ? dwBindVerb : BINDVERB_POST);
	m_hDataToPost = NULL;
	m_cbDataToPost = 0;
	m_fRedirect = FALSE;
}  // CBindStatusCallback

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::Create
// ---------------------------------------------------------------------------
HRESULT CBindStatusCallback::Create(CStatus* pStatus, LPCTSTR szData, BINDVERB dwBindVerb, IBindStatusCallback** ppBindStatusCallback)
{
	HRESULT hr;
	CBindStatusCallback* pBindStatusCallback;

	if (!ppBindStatusCallback)
	{
		return E_POINTER;
	}
	
	*ppBindStatusCallback = NULL;

	pBindStatusCallback = new CBindStatusCallback(pStatus, dwBindVerb);
	if (!pBindStatusCallback)
	{
		return E_OUTOFMEMORY;
	}

	hr = pBindStatusCallback->Init(szData);
	if (FAILED(hr))
	{
		delete pBindStatusCallback;
		return hr;
	}

	*ppBindStatusCallback = (IBindStatusCallback*)pBindStatusCallback;
	return NOERROR;
}

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::Init
// ---------------------------------------------------------------------------
HRESULT CBindStatusCallback::Init(LPCTSTR szData)
{
	if (m_hDataToPost)
	{
		// We're already in use and don't support recycling the same instance
		// Some other client may have a reference on us.
		// If we were to free this data, the client might crash dereferencing the handle
		return E_FAIL; 
	}

	if (szData)
	{
		// MSINTERNAL: See CINetHttp::INetAsyncSendRequest (cnethttp.cxx) that URLMON calls CINetHttp::GetDataToSend() followed by a call to WININET's HttpSendRequest(). GetDataToSend essentially pulls the data out of the BINDINFO that URLMON has cached away when it calls the host's implementation of IBindStatusCallback::GetBindInfo(). 
		// MSINTERNAL: It doesn't attempt to lock down the HGLOBAL at all, so we need to allocated GMEM_FIXED
		m_cbDataToPost = lstrlen(szData);
		m_hDataToPost = GlobalAlloc(GPTR, m_cbDataToPost+1); // GMEM_MOVEABLE won't work because URLMON doesn't attempt GlobalLock before dereferencing
		if (!m_hDataToPost)
		{
			return E_OUTOFMEMORY;
		}

		// the memory was allocate fixed, so no need to lock it down
		lstrcpy((LPTSTR)m_hDataToPost, szData);
	}

	return NOERROR;
}

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::QueryInterface
// ---------------------------------------------------------------------------
 STDMETHODIMP
CBindStatusCallback::QueryInterface(REFIID riid, void** ppv)
{
    *ppv = NULL;

    if (riid==IID_IUnknown || riid==IID_IBindStatusCallback)	
    {
	    *ppv = (IBindStatusCallback*)this;
		AddRef();
		return S_OK;
	}
	else if (riid==IID_IHttpNegotiate)
	{
	    *ppv = (IHttpNegotiate*)this;
		AddRef();
		return S_OK;
	}
	else
	{
		return E_NOINTERFACE;
	}

}  // CBindStatusCallback::QueryInterface

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::OnStartBinding
// ---------------------------------------------------------------------------
 STDMETHODIMP
CBindStatusCallback::OnStartBinding(DWORD dwReserved, IBinding* pbinding)
{
    if (NULL != m_pbinding)
	{
        m_pbinding->Release();
	}

	// Cache the URLMON-provided IBinding interface so that we can control the download
    m_pbinding = pbinding;

    if (m_pbinding != NULL)
	{
        m_pbinding->AddRef();
        SetStatusText(_T("Starting to bind..."));
	}

    return S_OK;
}  // CBindStatusCallback::OnStartBinding

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::GetPriority
// ---------------------------------------------------------------------------
 STDMETHODIMP
CBindStatusCallback::GetPriority(LONG* pnPriority)
{
	return E_NOTIMPL;
}  // CBindStatusCallback::GetPriority

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::OnLowResource
// ---------------------------------------------------------------------------
 STDMETHODIMP
CBindStatusCallback::OnLowResource(DWORD dwReserved)
{
    return E_NOTIMPL;
}  // CBindStatusCallback::OnLowResource

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::OnProgress
// ---------------------------------------------------------------------------
STDMETHODIMP CBindStatusCallback::OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText)
{
	TCHAR szStatusMsg[255] = _T("");
    TCHAR szProgress[255];

	switch (ulStatusCode)
	{
	case BINDSTATUS_REDIRECTING: 
		lstrcpy(szStatusMsg, _TEXT("Server redirecting client..."));
		ODS("Server redirecting client\n");
		m_fRedirect = TRUE;
		break;
	case BINDSTATUS_FINDINGRESOURCE:
		lstrcpy(szStatusMsg, _TEXT("Finding resource..."));
		ODS("Finding resource\n");
		break;
	case BINDSTATUS_CONNECTING:
		lstrcpy(szStatusMsg, _TEXT("Connecting..."));
		ODS("Connecting...\n");
		break;
	case BINDSTATUS_BEGINDOWNLOADDATA:
		lstrcpy(szStatusMsg, _TEXT("Beginning to download data..."));
		ODS("Beginning to download data...\n");
		break;
	case BINDSTATUS_DOWNLOADINGDATA:
		lstrcpy(szStatusMsg, _TEXT("Downloading data..."));
		ODS("Downloading data...\n");
		break;
	case BINDSTATUS_ENDDOWNLOADDATA:
		lstrcpy(szStatusMsg, _TEXT("Ending data download..."));
		ODS("Ending data download...\n");
		break;
	case BINDSTATUS_BEGINDOWNLOADCOMPONENTS:
		lstrcpy(szStatusMsg, _TEXT("Beginning to download components..."));
		ODS("Beginning to download components...\n");
		break;
	case BINDSTATUS_INSTALLINGCOMPONENTS:
		lstrcpy(szStatusMsg, _TEXT("Installing components..."));
		ODS("Installing components...\n");
		break;
	case BINDSTATUS_ENDDOWNLOADCOMPONENTS:
		lstrcpy(szStatusMsg, _TEXT("Ending component download..."));
		ODS("Ending component download...\n");
		break;
	case BINDSTATUS_USINGCACHEDCOPY:
		lstrcpy(szStatusMsg, _TEXT("Using cached copy..."));
		ODS("Using cached copy...\n");
		break;
	case BINDSTATUS_SENDINGREQUEST:
		lstrcpy(szStatusMsg, _TEXT("Sending request..."));
		ODS("Sending request...\n");
		break;
	case BINDSTATUS_CLASSIDAVAILABLE:
		lstrcpy(szStatusMsg, _TEXT("CLSID available..."));
		ODS("CLSID available...\n");
		break;
	case BINDSTATUS_MIMETYPEAVAILABLE:
		lstrcpy(szStatusMsg, _TEXT("MIME type available..."));
		ODS("MIME type available...\n");
		break;
	case BINDSTATUS_CACHEFILENAMEAVAILABLE:
		lstrcpy(szStatusMsg, _TEXT("Cache file name available..."));
		ODS("Cache file name available...\n");
		break;
	default:
		ODS("Unknown binding status code!\n");
		break;
	}

	SetStatusText(szStatusMsg);

    wsprintf(szProgress, "%d of %d", ulProgress, (ulProgress>ulProgressMax)?ulProgress:ulProgressMax);

	SetStatusText(szProgress, SBAR_PART_PROGRESS);
	UpdateProgress(ulProgress, ulProgressMax);

    return NOERROR;
}  // CBindStatusCallback::OnProgress

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::OnStopBinding
// ---------------------------------------------------------------------------
 STDMETHODIMP
CBindStatusCallback::OnStopBinding(HRESULT hrStatus, LPCWSTR pszError)
{
    if (hrStatus)
	{
        SetStatusText(_T("File download Failed."));
	}
	else
	{
		SetStatusText(_T("File download complete."));
	}

	if (m_pbinding)
	{
		m_pbinding->Release();
		m_pbinding = NULL;
	}

	if (m_pStatus) m_pStatus->OnDownloadComplete();

	ODS("OnStopBinding\n");

    return S_OK;
}  // CBindStatusCallback::OnStopBinding

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::GetBindInfo
// ---------------------------------------------------------------------------
 STDMETHODIMP
CBindStatusCallback::GetBindInfo(DWORD* pgrfBINDF, BINDINFO* pbindInfo)
{
	if (m_fRedirect && BINDVERB_POST == m_dwAction)
	{
		// We're being redirected by the server. Stop posting
		SetStatusText(_T("Switching method to GET"));
		m_dwAction = BINDVERB_GET;
	}

	*pgrfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;
	*pgrfBINDF |= BINDF_GETNEWESTVERSION | BINDF_NOWRITECACHE;

	// Set up the BINDINFO data structure
	pbindInfo->cbSize = sizeof(BINDINFO);
	pbindInfo->dwBindVerb = m_dwAction; // here's where the action is defined
	pbindInfo->szExtraInfo = NULL;

	// Initialize the STGMEDIUM.
	memset(&pbindInfo->stgmedData, 0, sizeof(STGMEDIUM));
	pbindInfo->grfBindInfoF = 0;
	pbindInfo->szCustomVerb = NULL;

	// set up action-specific members
	switch(m_dwAction)
	{
	case BINDVERB_POST:
		if (m_hDataToPost)
		{			
			// Fill the STGMEDIUM with the data to post
			pbindInfo->stgmedData.tymed = TYMED_HGLOBAL; // this is the only medium urlmon supports right now
			pbindInfo->stgmedData.hGlobal = m_hDataToPost;
			pbindInfo->stgmedData.pUnkForRelease = (LPUNKNOWN)(LPBINDSTATUSCALLBACK)this; //  maintain control over the data. 
			AddRef(); // It will be freed on final release
			pbindInfo->cbstgmedData = // this must be exact! 
				m_cbDataToPost;		  // Do not rely on GlobalSize() 
									  // which rounds up to the nearest power of two.
		}
		break;
	case BINDVERB_GET:
		break;
	default:
		return E_FAIL;
	}

	return S_OK;
}  // CBindStatusCallback::GetBindInfo

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::OnDataAvailable
// ---------------------------------------------------------------------------
 STDMETHODIMP
CBindStatusCallback::OnDataAvailable(DWORD grfBSCF, DWORD dwSize, FORMATETC* pfmtetc, STGMEDIUM* pstgmed)
{
	 HRESULT hr = S_OK;

    if (BSCF_FIRSTDATANOTIFICATION & grfBSCF)
    {
		// Cache the incoming Stream
        if (!m_pstm && pstgmed->tymed == TYMED_ISTREAM)
	    {
		    m_pstm = pstgmed->pstm;
            if (m_pstm)
                m_pstm->AddRef();
    	}
    }

    // If there is some data to be read then go ahead and read it
    if (m_pstm && dwSize > m_cbOld)
	{
        DWORD dwRead = dwSize - m_cbOld; // Minimum amount available that hasn't been read
        DWORD dwActuallyRead = 0;            // Placeholder for amount read during this pull

		if (dwRead > 0)
		do
		{
	        LPTSTR pNewstr = new char[dwRead + 1];
			if (!pNewstr)
			{
				// E_OUTOFMEMORY
				return S_FALSE;
			}

			hr= m_pstm->Read(pNewstr, dwRead, &dwActuallyRead);
		    pNewstr[dwActuallyRead] = '\0';

			// If we really read something then lets add it to the Edit box
			if (dwActuallyRead > 0)
			{
				if (m_pStatus)
				{
					hr = m_pStatus->AppendResponse(pNewstr);
					if (SUCCEEDED(hr))
					{
						m_cbOld += dwActuallyRead;
					}
				}
			}

			delete[] pNewstr;

		} while (!(hr == E_PENDING || hr == S_FALSE) && SUCCEEDED(hr));
	}

	// Clean up
	if (BSCF_LASTDATANOTIFICATION & grfBSCF)
	{
        if (m_pstm)
		{
            m_pstm->Release();
		}

		hr = S_OK;  // If it was the last data then we should return S_OK as we just finished reading everything

        SetStatusText(_T("File downloaded."));

		ODS("OnProgress: Last notification\n");
	}

    return hr;
}  // CBindStatusCallback::OnDataAvailable

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::OnObjectAvailable
// ---------------------------------------------------------------------------
 STDMETHODIMP
CBindStatusCallback::OnObjectAvailable(REFIID riid, IUnknown* punk)
{
    return E_NOTIMPL;
}  // CBindStatusCallback::OnObjectAvailable

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::BeginningTransaction
// ---------------------------------------------------------------------------
STDMETHODIMP CBindStatusCallback::BeginningTransaction(LPCWSTR szURL,
					LPCWSTR szHeaders,
					DWORD dwReserved,
					LPWSTR __RPC_FAR *pszAdditionalHeaders)
{
	// Here's our opportunity to add headers
	if (!pszAdditionalHeaders)
	{
		return E_POINTER;
	}

	*pszAdditionalHeaders = NULL;

	// This header is required when performing a POST operation
	if (BINDVERB_POST == m_dwAction && m_hDataToPost)
	{
		const WCHAR c_wszHeaders[] = 
			L"Content-Type: application/x-www-form-urlencoded\r\n";
		LPWSTR wszAdditionalHeaders = 
			(LPWSTR)CoTaskMemAlloc((wcslen(c_wszHeaders)+1) *sizeof(WCHAR));
		if (!wszAdditionalHeaders)
		{
			return E_OUTOFMEMORY;
		}

		wcscpy(wszAdditionalHeaders, c_wszHeaders);
		*pszAdditionalHeaders = wszAdditionalHeaders;
	}

	return NOERROR;
}        

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::OnResponse
// ---------------------------------------------------------------------------
STDMETHODIMP CBindStatusCallback::OnResponse(/* [in] */ DWORD dwResponseCode,
        /* [unique][in] */ LPCWSTR szResponseHeaders,
        /* [unique][in] */ LPCWSTR szRequestHeaders,
        /* [out] */ LPWSTR __RPC_FAR *pszAdditionalRequestHeaders)
{
	if (!pszAdditionalRequestHeaders)
	{
		return E_POINTER;
	}

	*pszAdditionalRequestHeaders = NULL;

	return NOERROR;
}
